package com.googlecode.contests_repo.validation;

import java.io.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import com.googlecode.contests_repo.core.model.Contest;
import com.googlecode.contests_repo.core.model.ResourcesUtils;

/**
 * A validation helper class used to validate the structure of contests.
 * 
 * @author joro, petko
 * 
 */
public class ContestValidator extends ResourceValidator {

	// Probably use apache.commons for this, but I found that faster
	private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
	private static final String TIMEZONE_STRING = "Zulu";

	/**
	 * Checks whether the given contest folder contains valid data for the
	 * contest. Uses contest-schema.json for the validation.
	 * 
	 * @param folderPath
	 *            the path to the folder resource to validate
	 * @return a list of all validation errors
	 */
	public synchronized List<String> validate(String folderPath) {
		List<String> errors = super.validate(folderPath);
		if (errors.size() == 1 && errors.get(0).contains("does not exist")) {
			return errors;
		}
		File folder = new File(folderPath);

		// order / score attributes validation
		ObjectMapper mapper = new ObjectMapper();
		int[] scores = null;
		String[] orders = null;
		String dateString = "";
		File contestFile = new File(folder, "contest.json");

		Contest contest = null;
		try {
			contest = mapper.readValue(contestFile, Contest.class);
			scores = contest.getProblemScores();
			orders = contest.getProblemOrder();
			dateString = contest.getStartTime();

			System.out.println(dateString);
		}
		// Swallow and add the message to the errors, also stop the validation
		catch (JsonMappingException e) {
			errors.add(e.getMessage());
			return errors;
		} catch (JsonParseException e) {
			errors.add(e.getMessage());
			return errors;
		} catch (IOException e) {
			errors.add(e.getMessage());
			return errors;
		}

		List<File> problemList = ValidationTools.getResourceSubfolders(folder);
		compareProblemOrderWithScores(errors, folder, problemList, orders,
				scores);

		// append contest problem errors
		ProblemValidator problemValidator = new ProblemValidator();
		for (File problem : problemList) {
			List<String> problemErrors = problemValidator.validate(problem
					.getAbsolutePath());
			errors.addAll(problemErrors);
		}

		// start time validation
		if (contestFile.exists() && !isDateStringValid(dateString)) {
			errors.add(String.format(
					"Folder %s: contest.json: malformatted start date time",
					folder.getAbsolutePath()));
		}

		totalFilesCount += problemList.size();

		// Ignore hidden files
		int expectedFilesCount = folder.listFiles(new FileFilter() {
			public boolean accept(File file) {
				return !file.isHidden();
			}
		}).length;

		if (expectedFilesCount != totalFilesCount) {
			errors.add(String.format("Folder %s contains superfluous files.",
					folder));
		}

		return errors;
	}

	protected boolean jsonExists(List<String> errors, File folder,
			List<File> fileList) {
		File contestJasonFile = new File(folder, "contest.json");
		if (!fileList.contains(contestJasonFile)) {
			errors.add(String
					.format("Folder %s does not contain a \"contest.json\" file definition.",
							folder.getAbsolutePath()));
			return false;
		} else {
			List<String> validationErrors = ValidationTools.validateJson(
					contestJasonFile, Contest.class);
			errors.addAll(validationErrors);
			return true;
		}
	}

	/**
	 * Checks whether the string representation of a date is valid
	 * 
	 * @param dateString
	 * @return true if the date string is correctly parsed
	 */
	public static boolean isDateStringValid(String dateString) {
		if (dateString == null) {
			return false;
		}

		DateFormat dateFormat = new SimpleDateFormat(DATE_TIME_PATTERN);
		dateFormat.setTimeZone(TimeZone.getTimeZone(TIMEZONE_STRING));

		try {
			dateFormat.parse(dateString);
		} catch (ParseException e) {
			return false;
		}

		return true;
	}

	private void compareProblemOrderWithScores(List<String> errors,
			File folder, List<File> problemList, String[] orders, int[] scores) {
		String problemFileMismatchMessage = getListedProblemsDoNotExistMessage(
				folder, problemList, orders);
		String orderScoreSizeMessage = getOrderScoreSizeMessage(folder, orders,
				scores);
		String scoreNumberMismatchMessage = getScoreNumberMismatchMessage(
				folder, problemList, orders, scores);
		
		String[] messages = new String[] { problemFileMismatchMessage,
				orderScoreSizeMessage, scoreNumberMismatchMessage };
		for (String message : messages) {
			if (!"".equals(message)) {
				errors.add(message);
			}
		}
	}

	/**
	 * Verifies whether the listed problem folder names in orders exist as
	 * subfolders in the folder specified by its relative path to the contest
	 * repo root.
	 * 
	 * @param relativeContestPath
	 *            the contest path relative to the repository root
	 * @param orders
	 *            the array of ordered problem folder names
	 * @return the error message if some problem folders do not exist and an
	 *         empty string otherwise
	 */
	public static String getListedProblemsDoNotExistMessage(
			String relativeContestPath, String[] orders) {
		String fullPath = ResourcesUtils
				.getFullResourcePath(relativeContestPath);
		File folder = new File(fullPath);
		if (!folder.exists()) {
			return "";
		}

		List<File> problemList = ValidationTools.getResourceSubfolders(folder);

		return getListedProblemsDoNotExistMessage(folder, problemList, orders);
	}

	private static String getListedProblemsDoNotExistMessage(File folder,
			List<File> problemList, String[] orders) {
		if (orders != null) {
			boolean foldersExist = ValidationTools.areFileNamesContained(
					Arrays.asList(orders), problemList);
			if (!foldersExist) {
				return String
						.format("Folder %s: The contest does not contain all problems mentioned in problem order.",
								folder.getAbsolutePath());
			}
		}

		return "";
	}

	/**
	 * Verifies whether the number of listed order entries matches the number of
	 * score entries for a contest specified by its relative path to the repo
	 * root.
	 * 
	 * @param relativeContestPath
	 *            the contest path relative to the repository root
	 * @param orders
	 *            the array of ordered problem folder names
	 * @param scores
	 *            the array of scores for the listed problems
	 * @return the error message if the array sizes are different and an empty
	 *         string otherwise
	 */
	public static String getOrderScoreSizeMessage(String relativeContestPath,
			String[] orders, int[] scores) {
		String fullPath = ResourcesUtils
				.getFullResourcePath(relativeContestPath);
		File folder = new File(fullPath);
		if (!folder.exists()) {
			return "";
		}

		return getOrderScoreSizeMessage(folder, orders, scores);
	}

	private static String getOrderScoreSizeMessage(File folder,
			String[] orders, int[] scores) {
		if (orders != null && scores != null && orders.length != scores.length) {
			return String
					.format("Folder %s: contest.json: problem_scores and problem_order have different sizes.",
							folder.getAbsolutePath());
		}

		return "";
	}

	/**
	 * Verify whether the scores array size matches the number of problem
	 * folders in the contest specified by its relative path to the repo root.
	 * 
	 * @param relativeContestPath
	 *            the contest path relative to the repository root
	 * @param orders
	 *            the array of ordered problem folder names
	 * @param scores
	 *            the array of scores for the listed problems
	 * @return the error message if the score array size does not match the
	 *         number of problem folders and an empty string otherwise
	 */
	public static String getScoreNumberMismatchMessage(
			String relativeContestPath, String[] orders, int[] scores) {
		String fullPath = ResourcesUtils
				.getFullResourcePath(relativeContestPath);
		File folder = new File(fullPath);
		if (!folder.exists()) {
			return "";
		}

		List<File> problemList = ValidationTools.getResourceSubfolders(folder);

		return getScoreNumberMismatchMessage(folder, problemList, orders,
				scores);
	}

	private static String getScoreNumberMismatchMessage(File folder,
			List<File> problemList, String[] orders, int[] scores) {
		if (scores != null && orders == null) {
			int problemCount = problemList.size();
			if (problemCount != scores.length) {
				return String
						.format("Folder %s: Number of scores does not match the number of contest problems.",
								folder.getAbsolutePath());
			}
		}

		return "";
	}
}
